#include "stdafx.h"
#include "thread/thread.hpp"
#include "thread/errorhandler.hpp"
#include "thread/threadlocal.hpp"
#include "thread/atomiccounter.hpp"
#include <sstream>

#if defined(JH_OS_WINDOWS)
/////////////////////////////////////////////////////////
//class CThreadImpl
/////////////////////////////////////////////////////////
namespace JHCPP
{
	namespace thread
	{
		CThreadImpl::CurrentThreadHolder CThreadImpl::m_currentThreadHolder;

		CThreadImpl::CThreadImpl()
		:m_pRunnableTarget(0),
		m_thread(0),
		m_threadId(0),
		m_prio(PRIO_NORMAL_IMPL),
		m_stackSize(JH_THREAD_STACK_SIZE)
		{
		}

		CThreadImpl::~CThreadImpl()
		{
			if (m_thread) 
			{
				::CloseHandle(m_thread);
				m_thread = 0;
			}
		}

		CThreadImpl::TIDImpl CThreadImpl::tidImpl() const
		{
			return m_threadId;
		}

		void CThreadImpl::setPriorityImpl(int prio)
		{
			if (prio != m_prio)
			{
				m_prio = prio;
				if (m_thread)
				{
					if (::SetThreadPriority(m_thread, m_prio) == 0)
						throw SystemException("cannot set thread priority");
				}
			}
		}

		int CThreadImpl::getPriorityImpl() const
		{
			return m_prio;
		}


		void CThreadImpl::setOSPriorityImpl(int prio, int /* policy */)
		{
			setPriorityImpl(prio);
		}

		int CThreadImpl::getOSPriorityImpl() const
		{
			return m_prio;
		}

		int CThreadImpl::getMinOSPriorityImpl(int /* policy */)
		{
			return PRIO_LOWEST_IMPL;
		}

		int CThreadImpl::getMaxOSPriorityImpl(int /* policy */)
		{
			return PRIO_HIGHEST_IMPL;
		}

		void CThreadImpl::setStackSizeImpl(int size)
		{
			m_stackSize = size;
		}

		int CThreadImpl::getStackSizeImpl() const
		{
			return m_stackSize;
		}

		void CThreadImpl::startImpl(CRunnable& target)
		{
			if (isRunningImpl())
				throw SystemException("thread already running");

			m_pRunnableTarget = &target;

			createImpl(runnableEntry, this);
		}

		void CThreadImpl::startImpl(Callable target, void* pData)
		{
			if (isRunningImpl())
				throw SystemException("thread already running");

			threadCleanup();
			m_callbackTarget.callback = target;
			m_callbackTarget.pData = pData;

			createImpl(callableEntry, this);
		}

		void CThreadImpl::joinImpl()
		{
			if (!m_thread) 
				return;

			switch (::WaitForSingleObject(m_thread, INFINITE))
			{
			case WAIT_OBJECT_0:
				threadCleanup();
				return;
			default:
				throw SystemException("cannot join thread");
			}
		}

		bool CThreadImpl::joinImpl(long milliseconds)
		{
			if (!m_thread) 
				return true;

			switch (::WaitForSingleObject(m_thread, milliseconds + 1))
			{
			case WAIT_TIMEOUT:
				return false;
			case WAIT_OBJECT_0:
				threadCleanup();
				return true;
			default:
				throw SystemException("cannot join thread");
			}
		}

		bool CThreadImpl::isRunningImpl() const
		{
			if (m_thread)
			{
				DWORD ec = 0;
				return ::GetExitCodeThread(m_thread, &ec) && ec == STILL_ACTIVE;
			}
			return false;
		}

		void CThreadImpl::sleepImpl(long milliseconds)
		{
			::Sleep(DWORD(milliseconds));
		}

		void CThreadImpl::yieldImpl()
		{
			::Sleep(0);
		}

		CThreadImpl* CThreadImpl::currentImpl()
		{
			return m_currentThreadHolder.get();
		}

		CThreadImpl::TIDImpl CThreadImpl::currentTidImpl()
		{
			return ::GetCurrentThreadId();
		}

		#if defined(_DLL)
		DWORD WINAPI CThreadImpl::runnableEntry(LPVOID pThread)
		#else
		unsigned __stdcall CThreadImpl::runnableEntry(void* pThread)
		#endif
		{
			m_currentThreadHolder.set(reinterpret_cast<CThreadImpl*>(pThread));

			try
			{
				reinterpret_cast<CThreadImpl*>(pThread)->m_pRunnableTarget->run();
			}
			catch (CException& exc)
			{
				CErrorHandler::handle(exc);
			}
			catch (std::exception& exc)
			{
				CErrorHandler::handle(exc);
			}
			catch (...)
			{
				CErrorHandler::handle();
			}
			return 0;
		}

		#if defined(_DLL)
		DWORD WINAPI CThreadImpl::callableEntry(LPVOID pThread)
		#else
		unsigned __stdcall CThreadImpl::callableEntry(void* pThread)
		#endif
		{
			m_currentThreadHolder.set(reinterpret_cast<CThreadImpl*>(pThread));

			try
			{
				CThreadImpl* pTI = reinterpret_cast<CThreadImpl*>(pThread);
				pTI->m_callbackTarget.callback(pTI->m_callbackTarget.pData);
			}
			catch (CException& exc)
			{
				CErrorHandler::handle(exc);
			}
			catch (std::exception& exc)
			{
				CErrorHandler::handle(exc);
			}
			catch (...)
			{
				CErrorHandler::handle();
			}
			return 0;
		}

		void CThreadImpl::createImpl(Entry ent, void* pData)
		{
		#if defined(_DLL)
			m_thread = ::CreateThread(NULL, m_stackSize, ent, pData, 0, &m_threadId);
		#else
			unsigned threadId;
			m_thread = (HANDLE) _beginthreadex(NULL, m_stackSize, ent, this, 0, &threadId);
			m_threadId = static_cast<DWORD>(threadId);
		#endif
			if (!m_thread)
				throw SystemException("cannot create thread");
			if (m_prio != PRIO_NORMAL_IMPL && !SetThreadPriority(m_thread, m_prio))
				throw SystemException("cannot set thread priority");
		}

		void CThreadImpl::threadCleanup()
		{
			if (!m_thread) 
				return;

			if (::CloseHandle(m_thread)) 
				m_thread = 0;
		}

	}//end of namespace thread
}//end of namespace JHCPP
#elif defined(JH_OS_LINUX)

#endif

/////////////////////////////////////////////////////////
//class CThread
/////////////////////////////////////////////////////////
namespace JHCPP
{
	namespace thread
	{
		CThread::CThread()
		: m_id(uniqueId()), 
		m_name(makeName()), 
		m_pTLS(0),
		m_event(true)
		{
		}

		CThread::CThread(const std::string& name)
		: m_id(uniqueId()), 
		m_name(name), 
		m_pTLS(0),
		m_event(true)
		{
		}

		CThread::~CThread()
		{
			clearTLS();
		}

		int CThread::id() const
		{
			return m_id;
		}

		CThread::TID CThread::tid() const
		{
			return CThreadImpl::tidImpl();
		}

		std::string CThread::name() const
		{
			CMutex::ScopedLock lock(m_mutex);
			return m_name;
		}

		std::string CThread::getName() const
		{
			CMutex::ScopedLock lock(m_mutex);
			return m_name;
		}

		void CThread::setName(const std::string& name)
		{
			CMutex::ScopedLock lock(m_mutex);
			m_name = name;
		}

		void CThread::setPriority(Priority prio)
		{
			CThreadImpl::setPriorityImpl(prio);
		}

		CThread::Priority CThread::getPriority() const
		{
			return Priority(CThreadImpl::getPriorityImpl());
		}
		
		void CThread::setOSPriority(int prio, int policy)
		{
			CThreadImpl::setOSPriorityImpl(prio, policy);	
		}

		int CThread::getOSPriority() const
		{
			return CThreadImpl::getOSPriorityImpl();
		}
	
		inline int CThread::getMinOSPriority(int policy)
		{
			return CThreadImpl::getMinOSPriorityImpl(policy);
		}

		int CThread::getMaxOSPriority(int policy)
		{
			return CThreadImpl::getMaxOSPriorityImpl(policy);
		}

		void CThread::setStackSize(int size)
		{
			CThreadImpl::setStackSizeImpl(size);
		}

		int CThread::getStackSize() const
		{
			return CThreadImpl::getStackSizeImpl();
		}

		void CThread::start(CRunnable& target)
		{
			 CThreadImpl::startImpl(target);
		}

		void CThread::start(Callable target, void* pData)
		{
			 CThreadImpl::startImpl(target, pData);
		}

		void CThread::join()
		{
			CThreadImpl::joinImpl();
		}

		void CThread::join(long milliseconds)
		{
			if (!CThreadImpl::joinImpl(milliseconds))
				throw TimeoutException();
		}

		bool CThread::tryJoin(long milliseconds)
		{
			return CThreadImpl::joinImpl(milliseconds);
		}

		bool CThread::isRunning() const
		{
			return CThreadImpl::isRunningImpl();
		}

		bool CThread::trySleep(long milliseconds)
		{
			CThread* pT = CThread::current();
			jh_check_ptr(pT);
			return !(pT->m_event.tryWait(milliseconds));
		}

		void CThread::wakeUp()
		{
			m_event.set();
		}

		void CThread::sleep(long milliseconds)
		{
			CThreadImpl::sleepImpl(milliseconds);
		}

		void CThread::yield()
		{
			CThreadImpl::yieldImpl();
		}

		CThread* CThread::current()
		{
			return static_cast<CThread*>(CThreadImpl::currentImpl());
		}

		CThread::TID CThread::currentTid()
		{
			return CThreadImpl::currentTidImpl();
		}

		CThreadLocalStorage& CThread::tls()
		{
			if (!m_pTLS)
				m_pTLS = new CThreadLocalStorage;
			return *m_pTLS;
		}

		void CThread::clearTLS()
		{
			if (m_pTLS)
			{
				delete m_pTLS;
				m_pTLS = 0;
			}
		}

		std::string CThread::makeName()
		{
			std::ostringstream name;
			name << '#' << m_id;
			return name.str();
		}

		int CThread::uniqueId()
		{
			static CAtomicCounter counter;
			return ++counter;
			return 0;
		}

	}//end of namespace thread
}//end of namespace JHCPP
